﻿using System.Collections.ObjectModel;
using System.Windows;

using Monti.DragAndDrop;
using Monti.DragAndDrop.Adorners;
using Monti.Utilities;

namespace Monti.Docking
{
   /// <summary>
   /// Handler for dock dropping.
   /// </summary>
   internal class DockingDropController : DropController
   {
      private bool m_IsToolDrop;
      private int m_InsertIndex;
      private double m_TabPanelHeight;
      private bool m_UseInsertionAdorner;
      private ToolDockingAdorner m_ToolAdorner;
      private DockArea m_DockArea = DockArea.Unknown;

      /// <summary>
      /// Gets the drop target as a dock site.
      /// </summary>
      private DockSite DockSite
      {
         get { return this.DropTarget as DockSite; }
      }
      
      /// <summary>
      /// Handles the drop operation.
      /// </summary>
      /// <param name="mousePoint">Position of mouse relative to drop target.</param>
      protected override void Drop (Point mousePoint)
      {
         var newSite = this.DockSite;
         var oldSite = this.DragController.DragSource as DockSite;
         if (newSite != null && oldSite != null)
         {
            var newItems = newSite.ItemsSource as Collection<object>;
            var oldItems = oldSite.ItemsSource as Collection<object>;
            if (oldItems == null || newItems == null) return;

            var dockItem = this.DragController.DragData;
            if (dockItem == null) return;
            int orgIndex = oldItems.IndexOf (dockItem);

            if (this.m_IsToolDrop)
            {
               if (orgIndex != -1) oldItems.RemoveAt (orgIndex);
               newSite.DockMan.AddToolWindow (dockItem, this.m_DockArea);
            }
            else if (this.m_UseInsertionAdorner)
            {
               if (newSite == oldSite)
               {
                  if (orgIndex < this.m_InsertIndex)
                     this.m_InsertIndex--;
                  if (this.m_InsertIndex == orgIndex)
                     return;
               }
               if (this.m_InsertIndex > newItems.Count) return;
               if (orgIndex != -1) oldItems.RemoveAt (orgIndex);
               newItems.Insert (this.m_InsertIndex, dockItem);
            }
            else
            {
               if (this.m_DockArea == DockArea.Unknown) return;
               if (orgIndex != -1) oldItems.RemoveAt (orgIndex);
               if (this.m_DockArea == DockArea.Document)
                  newItems.Insert (0, dockItem);
               else
               {
                  var newGroup = new DockGroup { DockArea = this.m_DockArea };
                  newGroup.Items.Add (dockItem);
                  newSite.Group.Children.Add (newGroup);
               }
            }
         }
      }

      /// <summary>
      /// Occurs when the drop target changes.
      /// </summary>
      protected override void DropTargetChanged ()
      {
         this.RemoveAdorner ();
         this.m_UseInsertionAdorner = true;
         this.m_TabPanelHeight = 30;
         DockSite site = this.DockSite;
         if (site != null)
         {
            var container = site.ItemContainerGenerator.ContainerFromIndex (0);
            if (container != null)
            {
               var panel = container.FindVisualParent<ScrollableTabPanel> ();
               if (panel != null)
                  this.m_TabPanelHeight = panel.RenderSize.Height;
            }
         }
      }

      /// <summary>
      /// Creates the drop adorner.
      /// </summary>
      protected override void CreateAdorner ()
      {
         var dockSite = this.DockSite;
         if (dockSite == null || 
             this.DragController.OriginalDragElementType == "System.Windows.Controls.TextBoxView")
         {
            this.RemoveAdorner ();
            return;
         }
          
         if (this.DropTarget == null) return;
         if (this.m_UseInsertionAdorner)
            this.DropAdorner = new InsertAdorner (this.DropTarget) { Height = this.m_TabPanelHeight };
         else
         {
            var adorner = new DockingAdorner (this.DropTarget) 
               {
                  Width = dockSite.RenderSize.Width,
                  Height = dockSite.RenderSize.Height,
                  Position = new Point(0, 0)
               };
            this.DropAdorner = adorner;
         }
         this.m_ToolAdorner = new ToolDockingAdorner (this.DockSite.DockMan)
            {
               Width = dockSite.DockMan.RenderSize.Width,
               Height = dockSite.DockMan.RenderSize.Height,
               Position = new Point (0, 0)
            };
      }

      /// <summary>
      /// Removes the drag adorners.
      /// </summary>
      public override void RemoveAdorner ()
      {
         if (this.m_ToolAdorner != null)
         {
            this.m_ToolAdorner.Detach ();
            this.m_ToolAdorner = null;
         }
         base.RemoveAdorner ();
      }

      /// <summary>
      /// Updates the attached adorner.
      /// </summary>
      /// <param name="mousePoint">Position of mouse relative to drop target.</param>
      protected override void UpdateAdorner (Point mousePoint)
      {
         if (this.m_UseInsertionAdorner && mousePoint.Y > this.m_TabPanelHeight)
         {
            this.RemoveAdorner ();
            this.m_UseInsertionAdorner = false;
            this.CreateAdorner ();
         }
         else if (!this.m_UseInsertionAdorner && mousePoint.Y <= this.m_TabPanelHeight)
         {
            this.RemoveAdorner ();
            this.m_UseInsertionAdorner = true;
            this.CreateAdorner ();
         }

         if (this.m_UseInsertionAdorner)
            this.UpdateInsertionAdorner (mousePoint);
         else
            this.UpdateDockAdorner (mousePoint);
      }

      /// <summary>
      /// Updates the dock adorner.
      /// </summary>
      /// <param name="mousePoint">Position of mouse relative to drop target.</param>
      private void UpdateDockAdorner (Point mousePoint)
      {
         // We need to update the dock positions based on the mouse Point.
         var adorner = this.DropAdorner as DockingAdorner;
         var site = this.DockSite;
         this.m_DockArea = DockArea.Unknown;
         if (adorner != null && site != null)
         {
            double dockHintStartX = site.RenderSize.Width / 2 - 44;
            double dockHintEndX = dockHintStartX + 88;
            double dockHintStartY = site.RenderSize.Height / 2 - 44;
            double dockHintEndY = dockHintStartY + 88;

            if (UpdateToolDockAdorner (site)) return;

            if (mousePoint.X > dockHintStartX && mousePoint.X < dockHintEndX && 
                mousePoint.Y > dockHintStartY && mousePoint.Y < dockHintEndY)
            {
               bool top = (mousePoint.X > dockHintStartX + 29) && (mousePoint.X < dockHintEndX - 29) &&
                          (mousePoint.Y < dockHintStartY + 29);
               if (top)
               {
                  this.m_DockArea = DockArea.Top;
                  adorner.DockTop = true;
                  return;
               }

               bool bot = (mousePoint.X > dockHintStartX + 29) && (mousePoint.X < dockHintEndX + 29) &&
                          (mousePoint.Y > dockHintEndY - 29);
               if (bot)
               {
                  this.m_DockArea = DockArea.Bottom;
                  adorner.DockBottom = true;
                  return;
               }

               bool left = (mousePoint.Y > dockHintStartY + 29) && (mousePoint.Y < dockHintEndY - 29) &&
                           (mousePoint.X < dockHintStartX + 29);
               if (left)
               {
                  this.m_DockArea = DockArea.Left;
                  adorner.DockLeft = true;
                  return;
               }

               bool right = (mousePoint.Y > dockHintStartY + 29) && (mousePoint.Y < dockHintEndY - 29) &&
                            (mousePoint.X > dockHintEndX - 29);
               if (right)
               {
                  this.m_DockArea = DockArea.Right;
                  adorner.DockRight = true;
                  return;
               }

               bool mid = (mousePoint.X > dockHintStartX + 29) && (mousePoint.X < dockHintEndX - 29) &&
                          (mousePoint.Y > dockHintStartY + 29) && (mousePoint.Y < dockHintEndY - 29);
               if (mid)
               {
                  this.m_DockArea = DockArea.Document;
                  adorner.DockTab = true;
                  return;
               }
            }
            this.m_ToolAdorner.HideDockHint ();
            adorner.HideDockHint ();
         }
      }

      private bool UpdateToolDockAdorner (DockSite site)
      {
         this.m_IsToolDrop = false;
         var dockMan = site.DockMan;
         var mousePoint = dockMan.RealMousePosition ();
         var dockManWidth = dockMan.RenderSize.Width;
         var dockManHeight = dockMan.RenderSize.Height;
         var midHorStart = dockManHeight / 2 - 15;
         var midVerStart = dockManWidth / 2 - 15;
         var midHorEnd = midHorStart + 30;
         var midVerEnd = midVerStart + 30;

         if (mousePoint.X < 29 && mousePoint.Y > midHorStart && mousePoint.Y < midHorEnd && !dockMan.HasLeftToolWindow)
         {
            this.m_IsToolDrop = true;
            this.m_DockArea = DockArea.Left;
            this.m_ToolAdorner.DockLeft = true;
            return true;
         }
         if (mousePoint.X > dockManWidth - 29 && mousePoint.X < dockManWidth &&
             mousePoint.Y > midHorStart && mousePoint.Y < midHorEnd && !dockMan.HasRightToolWindow)
         {
            this.m_IsToolDrop = true;
            this.m_DockArea = DockArea.Right;
            this.m_ToolAdorner.DockRight = true;
            return true;
         }
         if (mousePoint.Y < 29 && mousePoint.X > midVerStart && mousePoint.X < midVerEnd && !dockMan.HasTopToolWindow)
         {
            this.m_IsToolDrop = true;
            this.m_DockArea = DockArea.Top;
            this.m_ToolAdorner.DockTop = true;
            return true;
         }
         if (mousePoint.Y > dockManHeight - 29 && mousePoint.Y < dockManHeight &&
             mousePoint.X > midVerStart && mousePoint.X < midVerEnd && !dockMan.HasBottomToolWindow)
         {
            this.m_IsToolDrop = true;
            this.m_DockArea = DockArea.Bottom;
            this.m_ToolAdorner.DockBottom = true;
            return true;
         }
         return false;
      }

      /// <summary>
      /// Updates the insertion adorner.
      /// </summary>
      /// <param name="mousePoint">Position of mouse relative to drop target.</param>
      private void UpdateInsertionAdorner (Point mousePoint)
      {
         var site = this.DockSite;
         var orgSite = this.DragController.DragSource as DockSite;
         if (site != null && orgSite != null)
         {
            if (UpdateToolDockAdorner (site)) return;
            this.m_ToolAdorner.HideDockHint ();
            var element = site.InputHitTest (mousePoint) as UIElement;
            if (element != null)
            {
               var item = element.FindVisualParent<DockContent> ();
               if (item != null)
               {
                  Point position = site.TranslatePoint (mousePoint, item);
                  int index = site.ItemContainerGenerator.IndexFromContainer (item);
                  this.m_InsertIndex = index;
                  double x = 0;
                  if (position.X > item.RenderSize.Width / 2)
                  {
                     x = item.RenderSize.Width;
                     this.m_InsertIndex++;
                  }
                  this.DropAdorner.Position = item.TranslatePoint (new Point (x, 0), site);
               }
            }
         }
      }
   }
}
